/*
  Copyright (C) 2015 Luca Donaggio
  Contact: Luca Donaggio <donaggio@gmail.com>
  All rights reserved.

  You may use this file under the terms of MIT license as follows:

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
*/

import QtQuick 2.0
import Sailfish.Silica 1.0
import "../components"
import "../lib/theme.js" as AppTheme

Page {
    id: page

    allowedOrientations: Orientation.Portrait | Orientation.Landscape

    Drawer {
        id: drawer

        anchors.fill: parent

        dock: (page.isPortrait ? Dock.Top : Dock.Left)
        backgroundSize: (page.isPortrait ? (parent.height * 2 / 3) : (parent.width / 2))
        background: SettingsUI {
            id: settingsUI

            anchors.fill: parent

            typeID: settings.typeID
            iterations: settings.iterations
            showInfo: settings.showInfo
            gradientID: settings.gradientID

            PullDownMenu {
                MenuItem {
                    text: qsTr("About")

                    onClicked: { pageStack.push(Qt.resolvedUrl("AboutPage.qml")); }
                }

                MenuItem {
                    text: qsTr("Reset position")

                    onClicked: {
                        settings.defaultPosition();
                        drawer.open = false;
                    }
                }
            }

            onTypeIDChanged: { settings.typeID = typeID; }
            onIterationsChanged: { settings.iterations = iterations; }
            onShowInfoChanged: { settings.showInfo = showInfo; }
            onGradientIDChanged: { settings.gradientID = gradientID; }
        }

        Item {
            id: fractalContainer

            anchors.fill: parent
            clip: true

            ShaderEffect {
                id: fractalRenderer

                property variant source: gradientSource
                property int iterations: settings.iterations
                property bool useEmulDouble: settings.useEmulDouble
                // Single precision
                property double centerOffsetX: settings.centerOffsetX
                property double centerOffsetY: settings.centerOffsetY
                property double zoom: settings.zoom
                // Emulated double precision
                property vector2d dsCenterOffsetX: Qt.vector2d(centerOffsetX, (centerOffsetX - centerOffsetX))
                property vector2d dsCenterOffsetY: Qt.vector2d(centerOffsetY, (centerOffsetY - centerOffsetY))
                property vector2d dsZoom: Qt.vector2d(zoom, (zoom - zoom))

                width: (page.isPortrait ? parent.height : parent.width)
                height: width
                anchors.centerIn: parent

                blending: false
                cullMode: ShaderEffect.BackFaceCulling
                fragmentShader: "
                    varying highp vec2 qt_TexCoord0;
                    uniform highp float qt_Opacity;
                    uniform sampler2D source;
                    uniform int iterations;
                    uniform bool useEmulDouble;
                    // Single precision
                    uniform highp float centerOffsetX;
                    uniform highp float centerOffsetY;
                    uniform highp float zoom;
                    // Emulated double precision
                    uniform highp vec2 dsCenterOffsetX;
                    uniform highp vec2 dsCenterOffsetY;
                    uniform highp vec2 dsZoom;

                    /*
                     * Double precision math emulation
                     *
                     * Based on Fortran-90 double-single package (see http://crd.lbl.gov/~dhbailey/mpdist/)
                     */
                    // Add: res = dsAdd(a, b) => res = a + b
                    highp vec2 dsAdd (highp vec2 dsa, highp vec2 dsb) {
                        highp vec2 dsc;
                        highp float t1, t2, e;

                        t1 = dsa.x + dsb.x;
                        e = t1 - dsa.x;
                        t2 = ((dsb.x - e) + (dsa.x - (t1 - e))) + dsa.y + dsb.y;

                        dsc.x = t1 + t2;
                        dsc.y = t2 - (dsc.x - t1);
                        return dsc;
                    }

                    // Subtract: res = dsSub(a, b) => res = a - b
                    highp vec2 dsSub (highp vec2 dsa, highp vec2 dsb) {
                        highp vec2 dsc;
                        highp float e, t1, t2;

                        t1 = dsa.x - dsb.x;
                        e = t1 - dsa.x;
                        t2 = ((-dsb.x - e) + (dsa.x - (t1 - e))) + dsa.y - dsb.y;

                        dsc.x = t1 + t2;
                        dsc.y = t2 - (dsc.x - t1);
                        return dsc;
                    }

                    // Compare: res = dsComp(a, b) => res = -1 if a < b
                    //                                    =  0 if a == b
                    //                                    =  1 if a > b
                    lowp float dsComp(highp vec2 dsa, highp vec2 dsb) {
                        if (dsa.x < dsb.x) return -1.;
                        else if (dsa.x == dsb.x) {
                            if (dsa.y < dsb.y) return -1.;
                            else if (dsa.y == dsb.y) return 0.;
                            else return 1.;
                        } else return 1.;
                    }

                    // Multiply: res = dsMul(a, b) => res = a * b
                    highp vec2 dsMul(highp vec2 dsa, highp vec2 dsb) {
                        highp vec2 dsc;
                        highp float c11, c21, c2, e, t1, t2;
                        highp float a1, a2, b1, b2, cona, conb, split = 8193.;

                        cona = dsa.x * split;
                        conb = dsb.x * split;
                        a1 = cona - (cona - dsa.x);
                        b1 = conb - (conb - dsb.x);
                        a2 = dsa.x - a1;
                        b2 = dsb.x - b1;

                        c11 = dsa.x * dsb.x;
                        c21 = a2 * b2 + (a2 * b1 + (a1 * b2 + (a1 * b1 - c11)));

                        c2 = dsa.x * dsb.y + dsa.y * dsb.x;

                        t1 = c11 + c2;
                        e = t1 - c11;
                        t2 = dsa.y * dsb.y + ((c2 - e) + (c11 - (t1 - e))) + c21;

                        dsc.x = t1 + t2;
                        dsc.y = t2 - (dsc.x - t1);

                        return dsc;
                    }

                    // Convert float into double-single
                    highp vec2 dsSet(highp float a) {
                        highp vec2 z;

                        z.x = a;
                        z.y = 0.0;

                        return z;
                    }

                    // Mandelbrot set computing function
                    highp float mandel() {
                        highp vec2 c = ((qt_TexCoord0 - vec2(0.75, 0.5)) * zoom) - vec2(centerOffsetX, centerOffsetY);
                        highp vec2 z = c;
                        for (int n = 0; n < iterations; n++) {
                            z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c;
                            /*
                             * Smooth-coloring algorithm
                             * Convert n into a float to svoid banding when coloring
                             * See: http://linas.org/art-gallery/escape/escape.html
                             */
                            if (length(z) > 4.0) return (float(n) + 1.0 - log(log(length(z))) / log(2.0));
                        }
                        return 0.0;
                    }

                    // Mandelbrot set computing function using emulated double precision
                    highp float eMandel(void) {
                        highp vec2 e_tx = dsSet(qt_TexCoord0.x);
                        highp vec2 e_ty = dsSet(qt_TexCoord0.y);

                        // compute position in complex plane from current pixel
                        highp vec2 cx = dsSub(dsMul(dsSub(e_tx, dsSet(0.75)), dsZoom), dsCenterOffsetX);
                        highp vec2 cy = dsSub(dsMul(dsSub(e_ty, dsSet(0.5)), dsZoom), dsCenterOffsetY);

                        highp vec2 tmp;

                        highp vec2 zx = cx;
                        highp vec2 zy = cy;
                        highp vec2 two = dsSet(2.0);

                        highp vec2 e_radius = dsSet(4.0);

                        for (int n = 0; n < iterations; n++) {
                            tmp = zx;
                            zx = dsAdd(dsSub(dsMul(zx, zx), dsMul(zy, zy)), cx);
                            zy = dsAdd(dsMul(dsMul(zy, tmp), two), cy);
                            if (dsComp(dsAdd(dsMul(zx, zx), dsMul(zy, zy)), e_radius) > 0.0) {
                                return (float(n) + 1.0 - log(log(length(vec2(zx.x, zy.x)))) / log(2.0));
                            }
                        }
                        return 0.;
                    }

                    void main() {
                        highp float n;

                        if (useEmulDouble) n = eMandel();
                        else n = mandel();
                        if (n == 0.0) gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0) * qt_Opacity;
                        else gl_FragColor = texture2D(source, vec2(0.0, (n / float(iterations)))) * qt_Opacity;
                    }
                "

                onStatusChanged: {
                    if ((status === ShaderEffect.Compiled) || (status === ShaderEffect.Error)) {
                        // DEBUG
//                        console.log(log);
                    }
                }
            }

            Image {
                anchors.centerIn: parent
                visible: !fractalRenderer.visible
                opacity: (fractalRenderer.visible ? 0.0 : 1.0)
                source: "image://theme/icon-l-play"

                Behavior on opacity { FadeAnimation { } }
            }

            PinchArea {
                id: zoomingArea

                property double _oldZoom

                anchors.fill: parent
                enabled: (page.state != "paused")

                onPinchStarted: {
                    _oldZoom = settings.zoom;
                    fractalContainer.state = "panAndZoom";
                }

                onPinchUpdated: {
                    if (((pinch.scale >= 1) && (settings.zoom > AppTheme.minZoom)) || ((pinch.scale < 1) && (settings.zoom < AppTheme.maxZoom))) {
                        settings.zoom = _oldZoom * ((pinch.scale >= 1) ? (1 / pinch.scale) : (2 - pinch.scale));
                        settings.centerOffsetX += ((pinch.center.x - pinch.previousCenter.x) / width) * settings.zoom;
                        settings.centerOffsetY += ((pinch.center.y - pinch.previousCenter.y) / height) * settings.zoom;
                    }
                }

                onPinchFinished: {
                    _oldZoom = 1.0;
                    fractalContainer.state = "";
                }

                MouseArea {
                    id: panningArea

                    property int _lastX
                    property int _lastY

                    anchors.fill: parent

                    onPressed: {
                         _lastX = mouse.x
                         _lastY = mouse.y
                    }

                    onPositionChanged: {
                        if ((fractalContainer.state != "panAndZoom") && (Math.sqrt(Math.pow((mouse.x - _lastX), 2) + Math.pow((mouse.y - _lastY), 2)) >= Theme.startDragDistance)) {
                            fractalContainer.state = "panAndZoom";
                        }
                        if (fractalContainer.state == "panAndZoom") {
                            if (page.state != "paused") {
                                settings.centerOffsetX += ((mouse.x - _lastX) / width) * settings.zoom;
                                settings.centerOffsetY += ((mouse.y - _lastY) / height) * settings.zoom;
                            }
                            _lastX = mouse.x;
                            _lastY = mouse.y;
                        }
                    }

                    onCanceled: { fractalContainer.state = ""; }

                    onClicked: {
                        if (fractalContainer.state != "panAndZoom") drawer.open = !drawer.open;
                        else fractalContainer.state = "";
                    }
                }
            }

            states: [
                State {
                    name: "panAndZoom"

                    PropertyChanges {
                        target: fractalRenderer
                        iterations: {
                            var tmpIterations = settings.iterations * 0.66;
                            if (tmpIterations <= AppTheme.minIterations) tmpIterations = AppTheme.minIterations;
                            return tmpIterations;
                        }
                        opacity: 0.7
                    }
                }
            ]
        }

        Rectangle {
            id: gradientSource

            property list<Gradient> gradients: [
                Gradient {
                    GradientStop { position: 0.0; color: "black"; }
                    GradientStop { position: 0.05; color: Qt.lighter("black", 0.2); }
                    GradientStop { position: 0.4; color: "skyblue"; }
                    GradientStop { position: 0.6; color: "midnightblue"; }
                    GradientStop { position: 1.0; color: "darkorange"; }
                },
                Gradient {
                    GradientStop { position: 0.0; color: "black"; }
                    GradientStop { position: 0.05; color: Qt.lighter("black", 0.2); }
                    GradientStop { position: 0.4; color: "lightsalmon"; }
                    GradientStop { position: 0.6; color: "darkred"; }
                    GradientStop { position: 1.0; color: "gold"; }
                },
                Gradient {
                    GradientStop { position: 0.0; color: "black"; }
                    GradientStop { position: 0.05; color: Qt.lighter("black", 0.2); }
                    GradientStop { position: 0.4; color: "lightseagreen"; }
                    GradientStop { position: 0.6; color: "seagreen"; }
                    GradientStop { position: 1.0; color: "blueviolet"; }
                }
            ]

            width: 1
            height: 640
            anchors { top: parent.top; right: parent.right; }
            visible: false
            layer { enabled: true; smooth: true; }

            gradient: gradients[settings.gradientID]
        }

        InfoPanel {
            id: infoPanel

            opacity: 0.6

            centerOffsetX: settings.centerOffsetX
            centerOffsetY: settings.centerOffsetY
            zoom: (Math.round((1 / settings.zoom) * 10) / 10)
            useEmulDouble: settings.useEmulDouble
            panAndZoom: (fractalContainer.state === "panAndZoom")
        }
    }

    states: [
        State {
            name: ""

            PropertyChanges {
                target: infoPanel
                visible: settings.showInfo
            }

            PropertyChanges {
                target: fractalRenderer
                visible: true
            }
        },

        State {
            name: "paused"
            when: (Qt.application.state !== Qt.ApplicationActive) || (page.status !== PageStatus.Active) || drawer.opened

            PropertyChanges {
                target: infoPanel
                visible: false
            }

            PropertyChanges {
                target: fractalRenderer
                visible: false
            }
        }
    ]
}


